Tech

Diary

Lecture

About Me

개발중

Cypress

JeongSeulho

2024년 01월 20일

준비중...
클립보드로 복사

0. 들어가며

Cypress를 통한 E2E 테스트에 대해 정리

1. Cypress

아래는 백엔드를 테스트 서버로 실행하지 않고 로컬에서 실행하며, E2E 실행 또한 클라우드 환경이 아닌 로컬에서 진행하는 경우를 설명

(1) Cypress 설치

로컬에서 테스트할 경우 웹 앱을 먼저 실행하고 테스트를 진행해야 한다, 이때 start-server-and-test를 사용하면 테스트 전에 서버를 실행하고 테스트를 진행이 보장된다.

copy
npm i -D cypress
npm i start-server-and-test

(2) head 모드, headless 모드

  • head 모드

    • cypress open 명령어로 실행
    • 브라우저 및 UI 구동
    • 디버깅 용이
  • headless 모드

    • cypress run 명령어로 실행
    • 브라우저 UI 없이 브라우저 엔진만 구동하고 CLI로 테스트 진행
    • 클라우드 환경에서 테스트 진행 시 사용

(3) Time Travel 기능

E2E 테스트 과정 중 일어나는 모든 화면을 스냅샷으로 저장하여 히스토리를 확인할 수 있다.

2. Cypress로 E2E 테스트 작성

(1) cypress 설정 파일

copy
// cypress.config.js
import { defineConfig } from 'cypress';

const baseUrl = 'http://localhost:5173';

export default defineConfig({
  e2e: {
    video: false,
    viewportWidth: 1200,
    viewportHeight: 1000,
    baseUrl,
    scrollBehavior: 'center', // selector를 선택했을 때 top으로 스크롤이 되는 문제가 발생. header 영역을 고려하기 위해서는 해당 설정이 필요
  },
  env: {
    baseUrl,
  },
});

(2) E2E 테스트 코드

  1. cy.으로 API를 사용
  2. . 또는 then을 사용한 체이닝 형태로 작성해야 함
  • 내부적으로 Promise를 사용하고 이를 subject 객체라 부름
  • 이러한 실행 결과 전달을 yield라고 함
  • 중간에 변수에 담아 사용할 수 없음
copy
// cypress/e2e/Login.cy.js

beforeEach(() => {
  // '/login' 페이지로 이동
  cy.visit('/login');
});

it('이메일을 입력하지 않고 로그인 버튼을 클릭할 경우 "이메일을 입력하세요" 경고 메세지가 노출된다', () => {
  cy.findByLabelText('로그인').click();

  cy.findByText('이메일을 입력하세요').should('exist');
});

it('비밀번호를 입력하지 않고 로그인 버튼을 클릭할 경우 "비밀번호를 입력하세요" 경고 메세지가 노출된다', () => {
  cy.findByLabelText('로그인').click();

  cy.findByText('비밀번호를 입력하세요').should('exist');
});

it('잘못된 양식의 이메일을 입력한 뒤 로그인 버튼을 클릭할 경우 "이메일 양식이 올바르지 않습니다" 경고 메세지가 노출된다', () => {
  cy.findByLabelText('이메일').type('wrongemail#mail.com');
  cy.findByLabelText('로그인').click();

  cy.findByText('이메일 양식이 올바르지 않습니다').should('exist');
});

it('회원 가입 클릭 시 회원 가입 페이지로 이동한다', () => {
  cy.findByText('회원가입').click();

  cy.url().should('eq', `${Cypress.env('baseUrl')}/register`);
});

it('성공적으로 로그인 되었을 경우 메인 홈 페이지로 이동하며, 사용자 이름 "Maria"와 장바구니 아이콘이 노출된다', () => {
  const username = 'maria@mail.com';
  const password = '12345';

  cy.findByLabelText('이메일').type(username);
  cy.findByLabelText('비밀번호').type(password);
  cy.findByLabelText('로그인').click();

  cy.url().should('eq', `${Cypress.env('baseUrl')}/`);
  cy.findByText('Maria').should('exist');
  cy.findByTestId('cart-icon').should('exist');
});

(3) 반복 쿼리 리팩토링

copy
beforeEach(() => {
  cy.visit('/login');
  // 자주 되는 쿼리를 미리 가져옴
  cy.findByLabelText('로그인').as('loginBtn');
});

it('이메일을 입력하지 않고 로그인 버튼을 클릭할 경우 "이메일을 입력하세요" 경고 메세지가 노출된다', () => {
  // get을 통해 가져온 쿼리를 사용
  cy.get('@loginBtn').click();
  cy.findByText('이메일을 입력하세요').should('exist');
});

(4) then 사용

  1. then을 사용한 경우 콜백 함수 파라미터로 JQuery 객체로 받으므로 JQuery 메서드를 사용할 수 있다.
  • cypress subject 객체로 파라미터를 사용하려면 wrap을 사용해야 한다.
  1. then내부에서 아무것도 리턴하지 않으면 이전의 subject 객체를 그대로 사용한다.
  • 아래 예에서 then 내부에서 리턴이 없으므로 findByLabelTextsubject 객체를 그대로 사용한다.
copy
cy.findByLabelText('이메일')
  .then(($email) => {
    // JQuery 객체로 받아서 JQuery 메서드를 사용할 수 있다.
    const cls = $email.attr('class');

    // JQuery 객체를 subject 객체로 사용하려면 wrap을 사용해야 한다.
    cy.wrap($email).click();
  })
  .click();

(5) Retry-ability

E2E 테스트 시에 API 응답을 기다리는 중이나 렌더링 중 등의 상황을 고려 하여 특정 시간동안 재시도를 하며 기본적으로 4초이다.

copy
// 만약 렌더링이 오래걸리는 컴포넌트라 판단 되면 재시도를 10초로 늘린다.
cy.findByLabelText('이메일', { timeout: 10000 });

API 종류와 Retry-ability

  • Query : 체이닝 로직을 재시도하며 실행 get, find 등
  • Assertion : 단얼을 수행하는 쿼리 should
  • Non-Query : 재시도하지 않고 한번만 실행 visit, click, then 등